#pragma once

#include <iostream>
#include <memory>
#include <algorithm>
#include <sys/select.h>

#include "Socket.hpp"
#include "Log.hpp"

using namespace SocketModule;
using namespace LogModule;

class SelectServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1; // 默认的文件描述符的值是 -1  OS不处理小于 0 的文件描述符
private:
    void HandlerEvents(fd_set rfds)
    {
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            // fd_array[i] 是否就绪
            if(FD_ISSET(fd_array[i], &rfds))
            {
                if (_listenSockfd->Sockfd() == fd_array[i])
                {
                    Accepter(); // 套接字 fd 等待的事件就绪
                }
                else
                {
                    HandlerIO(i); // 普通 fd 等待的事件就绪
                }
            }
        }
    }

    void Accepter()
    {
        Inet_Addr addr;
        SockSPtr fd = _listenSockfd->Accepter(&addr);
        LOG(DEBUG, "get a new link, client info %s:%d\n", addr.Ip().c_str(), addr.Port());
        // 将 fd 添加到 rfds 中，继续交给 select 等待
        // 这里从 1 开始, 0 默认被 listenfd 占用了
        bool flag = false;// 标记结束的方式
        for(int i = 1; i < gnum; i++)
        {
            if(fd_array[i] == gdefaultfd)
            {
                flag = true;
                fd_array[i] = fd->Sockfd();// ？
                break;
            }
        }
        // 当前 rfds 中的 文件描述符满了
        if(!flag)
        {
            LOG(WARNING, "Server is FUll\n");
            fd->Close();
        }
    }

    void HandlerIO(int index)
    {
        char buffer[4096];
        ssize_t n = ::recv(fd_array[index], buffer, sizeof(buffer) - 1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "[client say@] " << buffer << std::endl;
            std::string content = "<html><body><h1>hello select!<h1></body></html>";
            std::string echo_str = "HTTP/1.0 200 OK\r\n";
            echo_str += "Content-Type: text/html\r\n";
            echo_str += "Content-Length: " + std::to_string(content.size()) +"\r\n\r\n";
            echo_str += content;
            ::send(fd_array[index], echo_str.c_str(), echo_str.size(), 0);// 临时处理方案
        }
        else if(n == 0)
        {
            LOG(INFO, "client is quit...\n");
            // 关闭 fd
            ::close(fd_array[index]);
            // select 不在关心这个 fd 了
            fd_array[index] = gdefaultfd;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            // 关闭 fd
            ::close(fd_array[index]);
            // select 不在关心这个 fd 了
            fd_array[index] = gdefaultfd;
        }
    }

public:
    SelectServer(uint16_t port) : _port(port), _listenSockfd(std::make_unique<TcpSocket>())
    {
        _listenSockfd->BuildListenSocket(port);
    }

    void SelectInit()
    {
        // 将辅助 select 处理时间的文件描述符数组置 -1
        for (int i = 0; i < gnum; i++)
        {
            fd_array[i] = gdefaultfd;
        }
        // 默认将 监听套接字 添加到 fd_array
        fd_array[0] = _listenSockfd->Sockfd();
    }

    void Loop()
    {
        while (true)
        {
            // 对文件描述符进行初始化
            fd_set rfds;
            FD_ZERO(&rfds);

            // 将合理的文件描述符 添加到 rfds 中
            int max_fd = gdefaultfd; // 最大文件描述符
            for (int i = 0; i < gnum; i++)
            {
                if (fd_array[i] == gdefaultfd)
                    continue;
                // 这里一定是合法的文件描述符 -> 添加到 rfds
                FD_SET(fd_array[i], &rfds);
                max_fd = std::max(fd_array[i], max_fd);
            }

            // 等待时间
            struct timeval timeout = {3, 0};
            int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
            switch (n)
            {
            case 0:
                LOG(INFO, "timeout, %d:%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                LOG(ERROR, "select error!\n");
                break;
            default:
                HandlerEvents(rfds);
                LOG(DEBUG, "haved events ready, n: %d\n", n); // 当有事件就绪，如果不做处理，select 就会一直通知我处理
                break;
            }
        }
    }

    ~SelectServer() {}

private:
    uint16_t _port;                        // 服务端 端口
    std::unique_ptr<Socket> _listenSockfd; // Socket 对象

    // 辅助 select 判断 哪些文件描述符是合法的 数组
    int fd_array[gnum];
};